package com.compression.tool.compression;

import com.compression.tool.model.enums.TaskStatus;
import com.compression.tool.model.task.CompressionTask;
import com.compression.tool.util.CompressionUtils;
import com.compression.tool.util.FileUtils;
import org.apache.commons.compress.archivers.sevenz.*;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.attribute.FileTime;
import java.util.List;
import java.util.logging.Logger;

/**
 * @author 自然醒
 * @version 1.0
 */
public class SevenZipCompression implements CompressionStrategy {
    private static final Logger logger = Logger.getLogger(SevenZipCompression.class.getName());

    @Override
    public boolean compress(List<File> files, File outputFile, int compressionLevel, CompressionTask task) {
        logger.info("开始压缩...");
        logger.info("源文件: " + files);
        logger.info("输出文件: " + outputFile);
        logger.info("压缩级别: " + compressionLevel);
        long totalBytes = FileUtils.calculateFileTotalSize(files);
        long processedSize = 0;
        try (SevenZOutputFile sevenZOutputFile = new SevenZOutputFile(outputFile)) {

            //配置压缩参数
            configureCompression(sevenZOutputFile, compressionLevel);

            if (files == null || files.isEmpty()) {
                logger.warning("没有文件可压缩");
                if (task != null) {
                    CompressionUtils.notifyCompletion(task, false, "源文件列表为空");
                }
                return false;
            }

            for (File file : files) {
                if (!file.exists()) {
                    logger.warning("文件不存在：" + file);
                    continue;
                }
                if (task != null && task.isCancelled()) {
                    logger.info("任务被取消");
                    return false;
                }
                if (task != null && task.getStatus() == TaskStatus.PAUSED) {
                    logger.info("任务被暂停");
                    CompressionUtils.waitForResume(task);
                }
                if (file.isDirectory()) {
                    if (!addDirectoryToArchive(sevenZOutputFile, file, "", task, processedSize, totalBytes)) {
                        logger.warning("添加目录失败：" + file);
                    }
                } else {
                    if (!addFileToArchive(sevenZOutputFile, file, "", task, processedSize, totalBytes)) {
                        logger.warning("添加文件失败：" + file);
                    }
                }
                processedSize += FileUtils.calculateFileSize(file.toPath());
                if (task != null && processedSize > 0) {
                    double progress = (double) processedSize / totalBytes * 100;
                    task.updateProgress(processedSize);
                    logger.info("压缩进度: " + progress + "%");
                }
                logger.info("已处理文件: " + file);
            }

        } catch (IOException e) {
            logger.severe("7z压缩失败：" + e.getMessage());
            return false;
        } catch (InterruptedException e) {
            logger.warning("任务被暂停");
            return false;
        }
        long compressedSize = outputFile.length();
        double compressionRatio = CompressionUtils.calculateCompressionRatio(totalBytes, compressedSize);
        //任务完成的信息
        String message = String.format("ZIP压缩完成: %s (压缩比: %.2f, %s -> %s)",
                outputFile.getName(), compressionRatio,
                FileUtils.formatFileSize(totalBytes),
                FileUtils.formatFileSize(compressedSize));
        if (task != null) {
            CompressionUtils.notifyCompletion(task, true, message);
        }
        logger.info(message);
        return true;
    }

    @Override
    public boolean decompress(File compressedFile, File outputDir, CompressionTask task) {
        logger.info("开始解压...");
        logger.info("压缩文件: " + compressedFile.getAbsolutePath());
        logger.info("输出目录: " + outputDir.getAbsolutePath());

        //验证输入文件
        if (!compressedFile.exists() || !compressedFile.isFile()) {
            logger.warning("输入文件不存在或无效");
            if (task != null) {
                CompressionUtils.notifyCompletion(task, false, "输入文件不存在或无效");
            }
            return false;
        }
        //验证输出目录
        if (!outputDir.exists() || !outputDir.isDirectory()) {
            logger.warning("输出目录不存在或无效");
            if (task != null) {
                CompressionUtils.notifyCompletion(task, false, "输出目录不存在或无效");
            }
            return false;
        }

        SevenZFile sevenZFile = null;
        long totalSize = 0;
        long processedSize = 0;
        try {
            sevenZFile = new SevenZFile(compressedFile);
            //更新任务状态
            if (task != null) {
                task.setStatus(TaskStatus.RUNNING);
                task.updateProgress(0);
            }
            SevenZArchiveEntry entry;
            //第一次计算总大小显示进度
            sevenZFile = new SevenZFile(compressedFile);
            while ((entry = sevenZFile.getNextEntry()) != null) {
                if (!entry.isDirectory()) {
                    totalSize += entry.getSize();
                }
            }
            //重新创建开始重新解压
            sevenZFile.close();
            sevenZFile = new SevenZFile(compressedFile);
            //实际解压
            while ((entry = sevenZFile.getNextEntry()) != null) {
                if (task != null && task.isCancelled()) {
                    logger.info("任务被取消");
                    return false;
                }
                if (task != null && task.getStatus() == TaskStatus.PAUSED) {
                    logger.warning("任务被暂停");
                    CompressionUtils.waitForResume(task);
                    return false;
                }
                String entryName = entry.getName();
                File outputFile = new File(outputDir, entryName);
                try {
                    if (entry.isDirectory()) {
                        if (!outputFile.mkdirs()) {
                            logger.warning("创建目录失败：" + outputFile);
                        }
                    } else {
                        File parentDir = outputFile.getParentFile();
                        if (parentDir != null && !parentDir.exists() && !parentDir.mkdirs()) {
                            logger.warning("创建目录失败：" + parentDir);
                            continue;
                        }
                        try (FileOutputStream out = new FileOutputStream(outputFile)) {
                            byte[] buffer = new byte[8192];
                            int bytesRead;
                            long entrySize = entry.getSize();
                            long entryProcessedSize = 0;
                            while ((bytesRead = sevenZFile.read(buffer)) > 0) {
                                if (task != null && task.isCancelled()) {
                                    out.close();
                                    if (!outputFile.delete()) {
                                        logger.warning("删除文件失败：" + outputFile.getAbsolutePath());
                                    }
                                    logger.info("任务被取消");
                                    return false;
                                }
                                if (task != null && task.getStatus() == TaskStatus.PAUSED) {
                                    logger.warning("任务被暂停");
                                    CompressionUtils.waitForResume(task);
                                }
                                out.write(buffer, 0, bytesRead);
                                entryProcessedSize += bytesRead;
                                processedSize += bytesRead;
                                if (task != null && totalSize > 0) {
                                    task.updateProgress((int) (processedSize * 100 / totalSize));
                                }
                            }
                        }
                        logger.info("解压条目完成: " + entryName);
                    }
                } catch (IOException e) {
                    logger.severe("解压条目失败: " + entryName + " - " + e.getMessage());
                    if (task != null) {
                        CompressionUtils.notifyCompletion(task, false, "解压条目失败: " + entryName + " - " + e.getMessage());
                    }
                    return false;
                }
            }
            logger.info("7z 文件解压完成");
            if (task != null) {
                CompressionUtils.notifyCompletion(task, true, "解压完成");
            }
            return true;
        } catch (IOException e) {
            logger.severe("7z解压失败：" + e.getMessage());
            if (task != null) {
                CompressionUtils.notifyCompletion(task, false, "7z解压失败：" + e.getMessage());
                return false;
            }
        } catch (InterruptedException e) {
            logger.warning("任务被暂停");
        }
        finally {
            // 确保资源被关闭
            if (sevenZFile != null) {
                try {
                    sevenZFile.close();
                } catch (IOException e) {
                    logger.warning("关闭 SevenZFile 时出错: " + e.getMessage());
                }
            }
        }
        return false;
    }

    @Override
    public String[] getSupportedExtensions() {
        return new String[]{"7z"};
    }

    @Override
    public String getFormatName() {
        return "7-Zip";
    }

    /**
     * 添加目录到压缩包
     *
     * @param sevenZOutputFile
     * @param directory
     * @param basePath
     * @param task
     * @param processedSize
     * @param totalBytes
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    private boolean addDirectoryToArchive(SevenZOutputFile sevenZOutputFile, File directory, String basePath, CompressionTask task, long processedSize, long totalBytes) throws IOException, InterruptedException {
        try {
            //添加目录条目
            String entryName = basePath + directory.getName() + "/";
            SevenZArchiveEntry entry = new SevenZArchiveEntry();
            entry.setName(entryName);
            entry.setDirectory(true);
            entry.setLastModifiedTime(FileTime.fromMillis(directory.lastModified()));

            sevenZOutputFile.putArchiveEntry(entry);
            sevenZOutputFile.closeArchiveEntry();

            //递归处理目录
            File[] files = directory.listFiles();
            if (files != null) {
                for (File file : files) {
                    if (task != null && task.isCancelled()) {
                        return false;
                    }
                    if (file.isDirectory()) {
                        if (!addDirectoryToArchive(sevenZOutputFile, file, entryName, task, processedSize, totalBytes)) {
                            logger.warning("添加目录失败：" + file);
                            return false;
                        } else {
                            if (!addFileToArchive(sevenZOutputFile, file, entryName, task, processedSize, totalBytes)) {
                                logger.warning("添加文件失败：" + file);
                                return false;
                            }
                        }
                    }
                }
            }
            logger.info("添加目录成功：" + directory);
            return true;
        } catch (IOException e) {
            logger.severe("添加目录失败：" + e.getMessage());
            return false;
        }
    }

    /**
     * 添加文件到压缩包
     *
     * @param sevenZOutputFile
     * @param file
     * @param basePath
     * @param task
     * @param processedSize
     * @param totalBytes
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    private boolean addFileToArchive(SevenZOutputFile sevenZOutputFile, File file, String basePath, CompressionTask task, long processedSize, long totalBytes) throws IOException, InterruptedException {
        try {
            String entryName = basePath + file.getName();
            SevenZArchiveEntry entry = new SevenZArchiveEntry();
            entry.setName(entryName);
            entry.setDirectory(false);
            entry.setLastModifiedTime(FileTime.fromMillis(file.lastModified()));
            sevenZOutputFile.putArchiveEntry(entry);

            //读取文件内容
            try (FileInputStream fis = new FileInputStream(file)) {
                byte[] buffer = new byte[8192];
                int bytesRead;
                long fileProcessed = 0;
                while ((bytesRead = fis.read(buffer)) != -1) {
                    if (task != null && task.isCancelled()) {
                        return false;
                    }
                    if (task != null && task.getStatus() == TaskStatus.PAUSED) {
                        CompressionUtils.waitForResume(task);
                    }
                    sevenZOutputFile.write(buffer, 0, bytesRead);
                    fileProcessed += bytesRead;
                    //更新进度
                    if (task != null && bytesRead > 0) {
                        task.updateProgress(fileProcessed);
                    }
                    logger.fine("已处理文件: " + fileProcessed + " bytes");
                }
            }
            sevenZOutputFile.closeArchiveEntry();
            logger.info("添加文件成功：" + file);
            return true;
        } catch (IOException e) {
            logger.severe("添加文件失败：" + e.getMessage());
            return false;
        }
    }

    /**
     * 配置压缩参数
     *
     * @param sevenZOutputFile
     * @param compressionLevel
     */
    private void configureCompression(SevenZOutputFile sevenZOutputFile, int compressionLevel) {
        //设置压缩方法
        SevenZMethod method = SevenZMethod.LZMA2;

        //根据压缩级别设置参数
        switch (compressionLevel) {
            case 0://无压缩 存储
                method = SevenZMethod.COPY;
                break;
            case 1://快速压缩
                sevenZOutputFile.setContentCompression(new SevenZMethodConfiguration(method, 1).getMethod());
                break;
            case 2: //标准压缩
                sevenZOutputFile.setContentCompression(new SevenZMethodConfiguration(method, 3).getMethod());
                break;
            case 3: //较好压缩
                sevenZOutputFile.setContentCompression(new SevenZMethodConfiguration(method, 5).getMethod());
                break;
            case 4: //最大压缩
                sevenZOutputFile.setContentCompression(new SevenZMethodConfiguration(method, 7).getMethod());
                break;
            case 5: //极限压缩
                sevenZOutputFile.setContentCompression(new SevenZMethodConfiguration(method, 9).getMethod());
                break;
            default: //默认压缩
                sevenZOutputFile.setContentCompression(new SevenZMethodConfiguration(method, 3).getMethod());
                break;
        }
        logger.info("压缩方法: " + method + "," + "压缩级别: " + compressionLevel);
    }


}
